package io.gitee.tziye.core.fs;

import io.gitee.tziye.common.RenaiUtil;
import io.gitee.tziye.core.id.RenaiIdSpec;
import io.gitee.tziye.core.instance.DefaultRenaiClient;
import io.gitee.tziye.enums.TimePattern;
import io.gitee.tziye.jdbc.JdbcProxy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

public class DefaultFsOperator extends FsOperator {

    private final static int SPLIT_SIZE = 15 * 1024 * 1024;

    private final JdbcProxy jdbcProxy;

    public DefaultFsOperator(DefaultRenaiClient renaiClient) {
        super(renaiClient);
        jdbcProxy = renaiClient.getJdbcProxy();
    }

    @Override
    public long save(String module, Map<String, Object> metadata, byte[] content) {
        assert StringUtils.isNotBlank(module) && content != null;
        if (metadata == null) {
            metadata = new HashMap<>();
        }
        RenaiFile renaiFile = new RenaiFile(module, metadata, content);
        List<RenaiFile> fileList = splitContent(renaiFile);
        long businessId = generateId();
        String metadataStr = RenaiUtil.string(metadata);
        jdbcProxy.doInTransaction(() -> {
            String sql = "INSERT INTO renai_file_system(module, business_id, seq, metadata, content) VALUES(?, ?, ?, ?, ?)";
            List<Future<Integer>> futures = new ArrayList<>();
            for (RenaiFile file : fileList) {
                Future<Integer> future = renaiClient.SCHEDULER.submit(() -> jdbcProxy.update(sql,
                        module, businessId, file.getSeq(), metadataStr, file.getContent()));
                futures.add(future);
            }
            for (Future<Integer> future : futures) {
                try {
                    future.get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            return null;
        });
        return businessId;
    }

    @Override
    public long save(String module, byte[] content) {
        return save(module, null, content);
    }

    private long generateId() {
        RenaiIdSpec idSpec = new RenaiIdSpec("RenaiFS", TimePattern.EPOCHM, 5);
        return renaiClient.id().generate(idSpec);
    }

    private List<RenaiFile> splitContent(RenaiFile renaiFile) {
        byte[] content = renaiFile.getContent();
        List<RenaiFile> fileList = new ArrayList<>();
        int len = content.length;
        int size = SPLIT_SIZE;
        int seq = 0;
        for (int i = 0; size == SPLIT_SIZE; i += SPLIT_SIZE) {
            size = Math.min(len - i, SPLIT_SIZE);
            if (size <= 0) {
                break;
            }
            byte[] cur = new byte[size];
            System.arraycopy(content, i, cur, 0, cur.length);
            RenaiFile newFile = renaiFile.copy(cur);
            newFile.setSeq(seq++);
            fileList.add(newFile);
        }
        return fileList;
    }

    private RenaiFile mergeContent(List<RenaiFile> fileList) {
        if (CollectionUtils.isEmpty(fileList)) {
            return null;
        }
        fileList = fileList.stream().sorted(Comparator.comparing(RenaiFile::getSeq)).toList();
        int len = fileList.stream().mapToInt(f -> f.getContent().length).sum();
        byte[] content = new byte[len];
        int i = 0;
        for (RenaiFile file : fileList) {
            System.arraycopy(file.getContent(), 0, content, i, file.getContent().length);
            i += file.getContent().length;
        }
        return fileList.get(0).copy(content);
    }

    @Override
    public RenaiFile query(long id) {
        String sql = "SELECT * FROM renai_file_system WHERE business_id = ?";
        List<RenaiFile> files = jdbcProxy.query(sql, new FileRowMapper(), id);
        return mergeContent(files);
    }

    @Override
    public List<RenaiFile> query(String module) {
        assert StringUtils.isNotBlank(module);
        String sql = "SELECT * FROM renai_file_system WHERE module = ?";
        List<RenaiFile> files = jdbcProxy.query(sql, new FileRowMapper(), module);
        return files.stream().collect(Collectors.groupingBy(RenaiFile::getBusinessId))
                .values().stream().map(this::mergeContent).toList();
    }

    @Override
    public boolean delete(long id) {
        String sql = "DELETE FROM renai_file_system WHERE business_id = ?";
        return jdbcProxy.update(sql, id) > 0;
    }

    @Override
    public int delete(String module) {
        assert StringUtils.isNotBlank(module);
        String sql = "DELETE FROM renai_file_system WHERE module = ?";
        return jdbcProxy.update(sql, module);
    }

    public static class FileRowMapper implements RowMapper<RenaiFile> {

        @Override
        public RenaiFile mapRow(ResultSet rs, int rowNum) throws SQLException {
            RenaiFile renaiFile = new RenaiFile();
            renaiFile.setId(rs.getLong("id"));
            renaiFile.setModule(rs.getString("module"));
            renaiFile.setBusinessId(rs.getLong("business_id"));
            renaiFile.setSeq(rs.getInt("seq"));
            String metadata = rs.getString("metadata");
            if (StringUtils.isNotBlank(metadata)) {
                renaiFile.setMetadata(RenaiUtil.parse(metadata, Map.class));
            }
            renaiFile.setContent(rs.getBytes("content"));
            renaiFile.setCreateTime(rs.getTimestamp("create_time"));
            return renaiFile;
        }
    }

}
